/*
 * Copyright (C) 2009 M. Hautle.
 * 
 * This file is part of TrackedBean.
 * 
 * TrackedBean is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any later version.
 * 
 * TrackedBean is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with TrackedBean. If not, see <http://www.gnu.org/licenses/>.
 */
package ch.trackedbean.util;

import java.beans.*;
import java.util.*;
import ch.trackedbean.common.*;

/**
 * Generic {@link Comparator} for elements.<br>
 * There is a default constructor for bean properties.
 * 
 * @param <T> The element type
 * @author M. Hautle
 */
public class ElementValueComparator<T> implements Comparator<T> {
	/** The accessors for the properties. */
	private final IValueAccessor<T>[] properties;

	/** Factor describing how the corresponding {@link #properties} entry will be sorted ascending (1) or descending (-1). */
	private final int[] order;

	/**
	 * Default constructor.
	 * 
	 * @param type The bean type
	 * @param properties The properties to compare (they have to implement {@link Comparable}!)
	 * @throws IllegalArgumentException If the specified bean has no such property
	 */
	@SuppressWarnings("unchecked")
	public ElementValueComparator(Class<T> type, String... properties) throws IllegalArgumentException {
		final Map<String, PropertyDescriptor> props = BeanUtils.getProperties(type);
		this.properties = new IValueAccessor[properties.length];
		this.order = new int[properties.length];
		for (int i = 0; i < properties.length; i++) {
			final PropertyDescriptor desc = props.get(properties[i]);
			if (desc == null)
				throw new IllegalArgumentException(type.getName() + " has no property " + properties[i] + "!");
			this.properties[i] = new PropertyAccessor<T>(desc);
			this.order[i] = 1;
		}
	}

	/**
	 * Default constructor.
	 * 
	 * @param accessors The accessors to use
	 */
	public ElementValueComparator(IValueAccessor<T>... accessors) {
		this.properties = accessors;
		order = new int[accessors.length];
		Arrays.fill(order, 1);
	}

	/**
	 * Sorts the corresponding property (order from the constructor) ascending/descending.<br>
	 * The value of the last parameter will be used for all other properties in the comparator.
	 * 
	 * @param ascending True to sort ascending false to sort descending - the position describes for which property the setting will be used
	 */
	public void setOrder(boolean... ascending) {
		for (int i = 0; i < ascending.length; i++)
			order[i] = ascending[i] ? 1 : -1;
		if (ascending.length == order.length)
			return;
		final int asc = ascending[ascending.length - 1] ? 1 : -1;
		Arrays.fill(order, ascending.length, order.length, asc);
	}

	/**
	 * Sets the sort order of the specified property.
	 * 
	 * @param index The index of the property (order from constructor)
	 * @param ascending True to sort ascending false to sort descending
	 */
	public void setOrder(int index, boolean ascending) {
		order[index] = ascending ? 1 : -1;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int compare(T o1, T o2) {
		if (o1 == o2)
			return 0;
		if (o1 == null)
			return -1;
		if (o2 == null)
			return -1;
		for (int i = 0; i < properties.length; i++) {
			try {
				final Comparable v1 = (Comparable) properties[i].getValue(o1, null);
				final Comparable v2 = (Comparable) properties[i].getValue(o2, null);
				final int res = compareProperty(v1, v2);
				if (res != 0)
					return res * order[i];
			} catch (Exception e) {
				throw new TechnicalException("Error while trying to read using " + properties[i].getPath(), e);
			}
		}
		return 0;
	}

	/**
	 * Compares two property values.
	 * 
	 * @param o1 The first value or null
	 * @param o2 The second value or null
	 * @return The comparation result
	 */
	@SuppressWarnings("unchecked")
	private int compareProperty(Comparable o1, Comparable o2) {
		if (o1 == o2)
			return 0;
		if (o1 == null)
			return -1;
		if (o2 == null)
			return -1;
		return o1.compareTo(o2);
	}
}
